package gweb

import (
	"encoding/json"
	"fmt"
	"net/http"
	"os"
	"strconv"

	"gitee.com/makitdone/gweb/v2/utils/structs"
	"gitee.com/makitdone/gx/jsonx"
	"github.com/gin-gonic/gin"
	"github.com/gin-gonic/gin/binding"
)

type Context struct {
	*gin.Context
	App   *GinApp
	Route *Route

	rawData []byte
	rawMap  map[string]any
}

func NewContext(app *GinApp, ctx *gin.Context) *Context {
	ctx.Set("app", app)
	return &Context{Context: ctx, App: app}
}

func (c *Context) GetClaim() *Claim {
	if claim, exists := c.Get("claim"); exists {
		return claim.(*Claim)
	} else {
		return nil
	}
}

func (c *Context) MustGetClaim() *Claim {
	claim, _ := c.Get("claim")
	if claim == nil {
		c.AbortWithStatus(401)
		return nil
	}
	return claim.(*Claim)
}

func (c *Context) BindUserClaim(dest any) error {
	claim := c.MustGetClaim()
	return claim.Bind(dest)
}

func (c *Context) GetData(key string, defaultVal any) any {
	if v, exists := c.Get(key); exists {
		return v
	} else {
		return defaultVal
	}
}

// 读取Body数据
func (c *Context) GetRawData() ([]byte, error) {
	if rawData, exists := c.Get("rawData"); exists {
		return rawData.([]byte), nil
	}
	rawData, err := c.Context.GetRawData()
	if err != nil {
		return nil, err
	}
	c.Set("rawData", rawData)
	return rawData, nil
}

func (c *Context) RawMap() map[string]any {
	if rawMap, exists := c.Get("rawMap"); exists {
		c.rawMap = rawMap.(map[string]any)
		rawData, _ := c.Get("rawData")
		c.rawData = rawData.([]byte)
		return c.rawMap
	} else {
		rawData, err := c.GetRawData()
		if err != nil {
			panic(err)
		}
		var rawMap map[string]any
		if err := json.Unmarshal(rawData, &rawMap); err != nil {
			panic(err)
		}
		c.Set("rawMap", rawMap)
		c.rawData = rawData
		c.rawMap = rawMap
	}
	return c.rawMap
}

// 根据请求方式处理数据绑定，一般用于GET和POST都支持的查询接口
func (c *Context) ShouldBind(obj any) map[string]any {
	switch c.Request.Method {
	case "POST":
		return c.ShouldBindJSON(obj)
	case "GET":
		c.ShouldBindQuery(obj)
	}
	return c.RawMap()
}

// 替代ShouldBindJSON，同时返回经过过滤的map数据
func (c *Context) ShouldBindJSON(obj any) map[string]any {
	rawMap := c.RawMap()
	if err := binding.JSON.BindBody(c.rawData, &obj); err != nil {
		panic(err)
	}
	return structs.FilterDataByObject(obj, rawMap)
}

func (c *Context) ShouldBindQuery(obj interface{}) {
	if err := c.Context.ShouldBindQuery(obj); err != nil {
		panic(err)
	}
}

func (c *Context) ShouldBindForm(key string, dest any) {
	valueStr := c.PostForm(key)
	if err := jsonx.Unmarshal(valueStr, &dest); err != nil {
		panic(err)
	}
}

// // 当前登录用户的ID
// func (c *Context) CurrentUserID() uint {
// 	claim := c.GetClaim()
// 	if claim == nil {
// 		panic("用户尚未登录")
// 	} else {
// 		return claim.UserID
// 	}
// }

func (c *Context) ParamInt(key string, defaultValue int) int {
	value := c.Param(key)
	if value == "" {
		return defaultValue
	}
	if intValue, err := strconv.Atoi(value); err == nil {
		return intValue
	} else {
		panic(err)
	}
}

func (c *Context) ParamFloat32(key string, defaultValue float32) float32 {
	value := c.Param(key)
	if value == "" {
		return defaultValue
	}
	if intValue, err := strconv.ParseFloat(value, 32); err == nil {
		return float32(intValue)
	} else {
		panic(err)
	}
}

func (c *Context) ParamFloat64(key string, defaultValue float64) float64 {
	value := c.Param(key)
	if value == "" {
		return defaultValue
	}
	if intValue, err := strconv.ParseFloat(value, 64); err == nil {
		return float64(intValue)
	} else {
		panic(err)
	}
}

func (c *Context) QueryInt(key string, defaultValue int64) int64 {
	value := c.Query(key)
	if value == "" {
		return defaultValue
	}
	if intValue, err := strconv.ParseInt(value, 10, 64); err != nil {
		panic(fmt.Sprintf("query value of the request is not int: %s", value))
	} else {
		return intValue
	}
}

func (c *Context) Success() {
	c.JSON(http.StatusOK, gin.H{
		"succeeded": true,
	})
}

func (c *Context) SuccessWithData(data any) {
	c.JSON(http.StatusOK, gin.H{
		"succeeded": true,
		"data":      data,
	})
}

func (c *Context) Fail(message string) {
	c.JSON(http.StatusOK, gin.H{
		"succeeded": false,
		"message":   message,
	})
}

func (c *Context) Failf(message string, args ...any) {
	c.JSON(http.StatusOK, gin.H{
		"succeeded": false,
		"message":   fmt.Sprintf(message, args...),
	})
}

func (c *Context) FailWithData(message string, data any) {
	c.JSON(http.StatusOK, gin.H{
		"succeeded": false,
		"message":   message,
		"data":      data,
	})
}

func (c *Context) Error(err string) {
	c.JSON(http.StatusOK, gin.H{
		"succeeded": false,
		"error":     err,
	})
}

func (c *Context) Errorf(err string, args ...any) {
	c.JSON(http.StatusOK, gin.H{
		"succeeded": false,
		"error":     fmt.Sprintf(err, args...),
	})
}

func (c *Context) File(filepath string, deleteAfterRespond bool) {
	c.Context.File(filepath)
	if deleteAfterRespond {
		os.Remove(filepath)
	}
}

func (c *Context) FileAttachment(filepath string, filename string, deleteAfterRespond bool) {
	c.Context.FileAttachment(filepath, filename)
	if deleteAfterRespond {
		os.Remove(filepath)
	}
}
